package cloud.tianai.nio.channel;

import cloud.tianai.nio.handler.ChannelPipeline;
import lombok.SneakyThrows;

import java.io.IOException;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;

public class NioSocketChannel extends AbstractNioChannel {

    /**
     * 多路复用器提供者
     */
    private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();

    /**
     * 开启一个 java SocketChannel 这个方法为客户端创建通道使用
     *
     * @param provider
     * @return
     */
    private static SocketChannel newSocket(SelectorProvider provider) {
        try {
            return provider.openSocketChannel();
        } catch (IOException e) {
            return null;
        }
    }

    public NioSocketChannel() {
        this(null, newSocket(DEFAULT_SELECTOR_PROVIDER));
    }

    /**
     * SocketChannel 感兴趣的事件是READ
     *
     * @param parent
     * @param channel
     */
    public NioSocketChannel(Channel parent, SocketChannel channel) {
        super(parent, channel, SelectionKey.OP_READ);
    }

    /**
     * 覆盖，因为可以确定返回的是SocketChannel
     *
     * @return
     */
    @Override
    public SocketChannel getJavaChannel() {
        return (SocketChannel) super.getJavaChannel();
    }

    /**
     * 绑定端socket
     *
     * @param localAddress
     * @throws Exception
     */
    @Override
    @SneakyThrows(IOException.class)
    public void bind(SocketAddress localAddress) {
        getJavaChannel().bind(localAddress);
    }

    @Override
    public void connect(SocketAddress address) throws IOException {
        getJavaChannel().connect(address);
        // sync
        while (false == getJavaChannel().finishConnect()){}
    }

    @Override
    public boolean isConnect() {
        return getJavaChannel().isConnected();
    }

    @Override
    public void write(ByteBuffer buffer) throws IOException{
        if (getJavaChannel().isConnected()) {
            getJavaChannel().write(buffer);
        }
    }

    @Override
    protected void doHandle(SelectionKey k) {
        final ChannelPipeline pipeline = this.getPipeline();
        ByteBuffer byteBuf = ByteBuffer.allocate(1024);
        doReadBytes(byteBuf);
        byteBuf.flip();
        pipeline.fireChannelRead(byteBuf);
    }

    protected int doReadBytes(ByteBuffer buf) {
        try {
            return getJavaChannel().read(buf);
        } catch (IOException e) {
            try {
                getJavaChannel().close();
            } catch (IOException ex) {
                // ignore
            }
            throw new RuntimeException(e);
        }
    }
}
